// Wintcy.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "Wintcy.h"
#include "HelperWindow.h"
#include "OpacityUtils.hpp"
#include "Utils.hpp"
#include <process.h>
#include <WinInet.h>

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR szCmd, int nCmdShow)
{
	MSG msg;
	
	if (!CheckForSupportedOS(hInstance) || !CheckForRunningInstances())
	{
		return FALSE;
	}
	
	SimpleList<SimpleString> args = SimpleString(szCmd).Split(' ', true);
	for (int i = 0; i < args.Count; i++)
	{
		if (args[i].Contains(_T("resident")))
		{
			bCloseToTray = true;			
		}
		else if (args[i].Contains(_T("noupdate")))
		{
			bNoUpdatesCheck = true;
		}
	}	
	
	// Initialize global strings
	szTitle = SimpleString::LoadFromResource(hInstance, IDS_APP_TITLE).ToCharArray();
	szWindowClass = SimpleString::LoadFromResource(hInstance, IDC_WINTCY).ToCharArray();
	szErrorCaption = SimpleString::LoadFromResource(hInstance, IDS_ERROR_CAPTION).ToCharArray();
	szStartupTaskName = SimpleString::LoadFromResource(hInstance, IDS_TASK_NAME).ToCharArray();
	
	RegisterMainWindow(hInstance);

	// Perform application initialization:
	if (!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!IsDialogMessage(msg.hwnd, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	if (hTempFile)
	{
		CloseHandle(hTempFile);
	}

	return (int)msg.wParam;
}

//
//   Performs a check if the current OS is supported.
//
bool CheckForSupportedOS(HINSTANCE hInstance)
{
	HMODULE hKernel32;

	// Check if app is running in Windows 8 or later.
	// According to MSDN, it is recommended to try to load an OS-specific API.
	hKernel32 = GetModuleHandle(_T("Kernel32.dll"));
	CreateFile2Fn = (CREATEFILE2FN)GetProcAddress(hKernel32, "CreateFile2");

	if (!CreateFile2Fn)
	{
		SimpleString caption = SimpleString::LoadFromResource(hInstance, IDS_UNSUPPORTED_CAPTION);
		SimpleString message = SimpleString::LoadFromResource(hInstance, IDS_UNSUPPORTED_MESSAGE);
		MessageBox(NULL, message.ToCharArray(), caption.ToCharArray(), MB_ICONERROR);
		return false;
	}

	return true;
}

//
//   Performs a check whether there are already running instances of the app.
//   Returns:
//     true, if there are no currently running instances; otherwise - false.
//
bool CheckForRunningInstances()
{
	TCHAR  szTempDir[MAX_PATH], szTempFile[MAX_PATH];
	DWORD  dwResult, dwError;
	UINT   uResult;
	CREATEFILE2_EXTENDED_PARAMETERS cfexp;

	dwResult = GetTempPath(MAX_PATH, szTempDir);
	if (0 == dwResult || dwResult > (MAX_PATH - 14))
	{
		dwError = GetLastError();
		return true;	// If we cannot create a locked file, we'll continue working,
						// though we won't be able to monitor other instances.
	}

	uResult = GetTempFileName(szTempDir, _T("OPP"), (UINT)MAKEWORD(0, 3), szTempFile);
	if (0 == uResult)
	{
		dwError = GetLastError();
		return true;	// The same as above
	}

	SecureZeroMemory(&cfexp, sizeof(CREATEFILE2_EXTENDED_PARAMETERS));
	cfexp.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
	cfexp.dwFileAttributes = FILE_ATTRIBUTE_TEMPORARY;
	cfexp.dwFileFlags = FILE_FLAG_DELETE_ON_CLOSE;

	if (!CreateFile2Fn)
	{
		return true;
	}

	hTempFile = CreateFile2Fn(szTempFile, FILE_ALL_ACCESS, 0, CREATE_ALWAYS, &cfexp);
	if (INVALID_HANDLE_VALUE == hTempFile)
	{
		return false;
	}

	return true;
}

//
//   Registers the window class.
//
WORD RegisterMainWindow(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;
	SecureZeroMemory(&wcex, sizeof(WNDCLASSEX));

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style         = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc   = WndProc;
	wcex.hInstance     = hInstance;
	wcex.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINTCY));
	wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm       = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   Saves instance handle and creates main window
//
bool InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND  hWnd;
	
	hInst = hInstance;

	hWnd = CreateWindowEx(WS_EX_DLGMODALFRAME, szWindowClass, szTitle,
		WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX & ~WS_THICKFRAME,
		CW_USEDEFAULT, CW_USEDEFAULT, 240, 240, HWND_DESKTOP, NULL, hInstance, NULL);

	if (!hWnd)
	{
		return false;
	}

	ShowWindow(hWnd, bCloseToTray ? SW_HIDE : nCmdShow);
	UpdateWindow(hWnd);
	
	return true;
}

//
//   Sets command link text and note.
//
static void SetButtonTextAndNote(HWND hButton, bool isEnableButton)
{
	static SimpleString captionEnable	= SimpleString::LoadFromResource(hInst, IDS_BTN_ENABLE);
	static SimpleString captionDisable	= SimpleString::LoadFromResource(hInst, IDS_BTN_DISABLE);
	static SimpleString noteEnable		= SimpleString::LoadFromResource(hInst, IDS_BTN_ENABLE_NOTE);
	static SimpleString noteDisable		= SimpleString::LoadFromResource(hInst, IDS_BTN_DISABLE_NOTE);
	
	SendMessage(hButton, WM_SETTEXT, 0,
		(LPARAM)(isEnableButton ? captionEnable.ToCharArray() : captionDisable.ToCharArray()));
	SendMessage(hButton, BCM_SETNOTE, 0,
		(LPARAM)(isEnableButton ? noteEnable.ToCharArray() : noteDisable.ToCharArray()));
	SetWindowLongPtr(hButton, GWLP_USERDATA, isEnableButton ? TRUE : FALSE);
}

//
//   Initializes controls inside main window.
//
bool InitControls(HWND hWnd)
{
	HWND hGrpSettings,
		 hChkRunAtStartup,
		 hChkCloseToTray,
		 hLnkFooter;	
	INITCOMMONCONTROLSEX icc;

	SecureZeroMemory(&icc, sizeof(icc));
	icc.dwICC = ICC_STANDARD_CLASSES | ICC_LINK_CLASS;
	icc.dwSize = sizeof(icc);
	if (!InitCommonControlsEx(&icc))
	{
		return false;
	}
	
	hOpacityButton = CreateWindow(WC_BUTTON, NULL,
		WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFCOMMANDLINK,
		12, 12, 200, 63, hWnd, (HMENU)IDB_BUTTON, hInst, NULL);

	SimpleString settings = SimpleString::LoadFromResource(hInst, IDS_SETTINGS);
	hGrpSettings = CreateWindow(WC_BUTTON, settings.ToCharArray(),
		BS_GROUPBOX | WS_VISIBLE | WS_CHILD | WS_GROUP,
		12, 85, 200, 79, hWnd, NULL, hInst, NULL);

	SimpleString chkRunAtStartup = SimpleString::LoadFromResource(hInst, IDS_CHK_RUNATSTARTUP);
	hChkRunAtStartup = CreateWindow(WC_BUTTON, chkRunAtStartup.ToCharArray(),
		WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
		25, 110, 143, 18, hWnd, (HMENU)IDB_CHKRUNAT, hInst, NULL);

	SimpleString closeToTray = SimpleString::LoadFromResource(hInst, IDS_CHK_CLOSETRAY);
	hChkCloseToTray = CreateWindow(WC_BUTTON, closeToTray.ToCharArray(),
		WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX,
		25, 135, 167, 18, hWnd, (HMENU)IDB_CHKTRAY, hInst, NULL);

	SimpleString bevel = SimpleString::LoadFromResource(hInst, IDS_FOOTER);
	hLnkFooter = CreateWindow(WC_LINK, bevel.ToCharArray(),
		WS_TABSTOP | WS_VISIBLE | WS_CHILD | LWS_RIGHT,
		12, 174, 200, 18, hWnd, (HMENU)IDL_FOOTER, hInst, NULL);

	if (!hOpacityButton || !hGrpSettings || !hChkRunAtStartup || !hChkCloseToTray || !hLnkFooter)
	{
		return false;
	}

	SetButtonTextAndNote(hOpacityButton, true);

	SetDefaultFont(hWnd);	
	SetDefaultFont(hOpacityButton);
	SetDefaultFont(hGrpSettings);
	SetDefaultFont(hChkRunAtStartup);
	SetDefaultFont(hChkCloseToTray);
	SetDefaultFont(hLnkFooter);

	if (GetRunAtStartup())
	{
		SendMessage(hChkRunAtStartup, BM_SETCHECK, BST_CHECKED, 0);
	}

	if (bCloseToTray)
	{
		SendMessage(hChkCloseToTray, BM_SETCHECK, BST_CHECKED, 0);
	}

	return true;
}

//
//   Callback for opacity changing event.
//
static inline void OpacityChangedCallback(bool isEnabled)
{
	SetButtonTextAndNote(hOpacityButton, !isEnabled);
}

//
//   Processes messages for the main window.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{	
	switch (message)
	{
	case WM_CREATE:
	{		
		if (!InitControls(hWnd))
		{
			return -1; // Destroy the window
		}
		OpacityUtils::SetOpacityChangedCallback(&OpacityChangedCallback);		
		try
		{
			CHelperWindow::GetInstance(hInst, hWnd).CreateNotifyIcon();
		}
		catch (HelperWindowCreationException &err)
		{			
			MessageBox(hWnd, err.Message.ToCharArray(), szErrorCaption, MB_ICONERROR);
		}
		OpacityUtils::FindTaskbars();
		OpacityUtils::ChangeOpacity(true);
		if (!bNoUpdatesCheck)
		{
			_beginthread(&CheckForUpdates, 0, NULL);
		}		
		break;
	}
	case WM_COMMAND:
		return ProcessWMCommand(hWnd, message, wParam, lParam);
	case WM_NOTIFY:
	{
		switch (((LPNMHDR)lParam)->code)
		{
		case NM_CLICK:
		case NM_RETURN:
		{
			PNMLINK pNmLink = (PNMLINK)lParam;
			if (IDL_FOOTER == pNmLink->hdr.idFrom)
			{
				LITEM lItem = pNmLink->item;
				OpenHyperlink(lItem.szUrl);
			}
			break;
		}
		}
		break;
	}
	case WM_CLOSE:
	{
		if (bCloseToTray)
		{
			ShowWindow(hWnd, SW_HIDE);
		}
		else
		{
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	}
	case WM_DESTROY:
		OpacityUtils::FindTaskbars();
		OpacityUtils::ChangeOpacity(false);
		PostQuitMessage(0);
		break;
	case WM_CTLCOLORSTATIC:
	case WM_CTLCOLORBTN:
		return (LRESULT)GetStockObject(COLOR_WINDOW + 1);
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

//
//   Processes WM_COMMAND message sent to main window.
//
LRESULT ProcessWMCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	LONG lButtonData;
	BOOL bIsChecked;

	wmId = LOWORD(wParam);
	wmEvent = HIWORD(wParam);

	switch (wmId)
	{
	case IDB_BUTTON:
		lButtonData = GetWindowLongPtr((HWND)lParam, GWLP_USERDATA);
		HandleButtonClick(TRUE == lButtonData);
		SetButtonTextAndNote((HWND)lParam, TRUE != lButtonData);
		break;
	case IDB_CHKRUNAT:
		bIsChecked = (BOOL)SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
		if (SetRunAtStartup(TRUE != bIsChecked))
		{
			SendMessage((HWND)lParam, BM_SETCHECK, (WPARAM)(bIsChecked ^ 1), 0);
		}
		break;
	case IDB_CHKTRAY:
		bIsChecked = (BOOL)SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
		bCloseToTray = TRUE == bIsChecked;
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}

	return 0;
}

//
//   Gets a value that indicates whether the app is set to run at startup.
//
bool GetRunAtStartup()
{
	HRESULT	hr;

	ITaskService	*pService;
	ITaskFolder		*pRootFolder;
	IRegisteredTask	*pRegisteredTask;

	hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		return false;
	}

	hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void **)&pService);
	if (FAILED(hr))
	{
		CoUninitialize();
		return false;
	}

	hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
	if (FAILED(hr))
	{
		pService->Release();
		CoUninitialize();
		return false;
	}

	hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
	pService->Release();
	if (FAILED(hr))
	{
		CoUninitialize();
		return false;
	}

	pRegisteredTask = NULL;
	hr = pRootFolder->GetTask(_bstr_t(szStartupTaskName), &pRegisteredTask);
	pRootFolder->Release();
	
	if (FAILED(hr) || !pRegisteredTask)
	{
		CoUninitialize();
		return false;
	}
	
	pRegisteredTask->Release();	
	CoUninitialize();

	return true;
}

//
//   Sets or removes system startup record for current app path.
//
bool SetRunAtStartup(bool isSet)
{
	HRESULT	hr;

	ITaskService	*pService;
	ITaskFolder		*pRootFolder;
	IRegisteredTask	*pRegisteredTask;

	static SimpleString &strXml = FormatTaskXml();
	static SimpleString &strUserId = GetUserId();

	hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (FAILED(hr))
	{
		return false;
	}

	hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void **)&pService);
	if (FAILED(hr))
	{
		CoUninitialize();
		return false;
	}

	hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
	if (FAILED(hr))
	{
		pService->Release();
		CoUninitialize();
		return false;
	}

	hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
	pService->Release();
	if (FAILED(hr))
	{
		CoUninitialize();
		return false;
	}
		
	pRootFolder->DeleteTask(_bstr_t(szStartupTaskName), 0);

	if (isSet)
	{
		hr = pRootFolder->RegisterTask(
			_bstr_t(szStartupTaskName), _bstr_t(strXml.ToCharArray()), TASK_CREATE_OR_UPDATE,
			_variant_t(strUserId.ToCharArray()), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN,
			_variant_t(), &pRegisteredTask);
		if (SUCCEEDED(hr))
		{
			pRegisteredTask->Release();
		}
	}

	pRootFolder->Release();
	CoUninitialize();

	return SUCCEEDED(hr);
}

//
//   Handles buttons' clicks.
//
void HandleButtonClick(bool doEnable)
{
	HRESULT hr;
	static SimpleString message			= SimpleString::LoadFromResource(hInst, IDS_ERROR_MESSAGE);
	static SimpleString captionEnable	= SimpleString::LoadFromResource(hInst, IDS_ERROR_CAPTION_ENABLE);
	static SimpleString captionDisable	= SimpleString::LoadFromResource(hInst, IDS_ERROR_CAPTION_DISABLE);

	OpacityUtils::FindTaskbars();
	hr = OpacityUtils::ChangeOpacity(doEnable);
	if (FAILED(hr))
	{
		MessageBox(NULL, message.ToCharArray(),
			doEnable ? captionEnable.ToCharArray() : captionDisable.ToCharArray(),
			MB_ICONERROR);
	}
}

//
//   Performs updates check for the program.
//
void __cdecl CheckForUpdates(void *)
{
	HINTERNET hInternet, hFile;
	DWORD	  dwBytesRead, dwVerInfoSize;
	TCHAR	  szFilename[MAX_PATH];
	BYTE	  *pVerBuf, *pValueBuf;
	DWORD	  dwUpdateMS, dwUpdateLS;
	bool	  bUpdateIsAvailable;
	
	hInternet = InternetOpen(NULL, 0, NULL, NULL, 0);

	if (!hInternet)
	{
		return;
	}
		
	SimpleString updateURL = SimpleString::LoadFromResource(hInst, IDS_UPDATE_URL);
	hFile = InternetOpenUrl(hInternet, updateURL.ToCharArray(), NULL, 0, INTERNET_FLAG_RELOAD |
		INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI, NULL);

	if (!hFile)
	{
		InternetCloseHandle(hInternet);
		return;
	}
	
	SimpleString fileBuffer = SimpleString();
	do
	{
		char buf[80]; // (UINT_MAX length + 1) * 4
		SecureZeroMemory(buf, 80);
		if (!InternetReadFile(hFile, buf, 80, &dwBytesRead))
		{
			break;
		}		

		TCHAR *tmp = new TCHAR[dwBytesRead + 1];		
		SecureZeroMemory(tmp, (dwBytesRead + 1) * sizeof(TCHAR));

		MultiByteToWideChar(CP_UTF8, 0, buf, dwBytesRead, tmp, dwBytesRead);
		fileBuffer.Append(tmp);

		delete[] tmp;
	} while (dwBytesRead > 0);

	InternetCloseHandle(hFile);
	hFile = INVALID_HANDLE_VALUE;
	InternetCloseHandle(hInternet);
	hInternet = INVALID_HANDLE_VALUE;
		
	SimpleList<SimpleString> split = fileBuffer.Split(_T('.'), true);
	if (split.Count != 4)
	{
		return;
	}
	
	dwUpdateMS = MAKELONG(StrToInt(split[1].ToCharArray()), StrToInt(split[0].ToCharArray()));
	dwUpdateLS = MAKELONG(StrToInt(split[3].ToCharArray()), StrToInt(split[2].ToCharArray()));
	
	SecureZeroMemory(szFilename, MAX_PATH);
	GetModuleFileName(NULL, szFilename, MAX_PATH);

	dwVerInfoSize = GetFileVersionInfoSize(szFilename, NULL);
	pVerBuf = new BYTE[dwVerInfoSize];
	bUpdateIsAvailable = false;
	if (GetFileVersionInfo(szFilename, NULL, dwVerInfoSize, (LPVOID)pVerBuf))
	{
		if (VerQueryValue(pVerBuf, L"\\", (LPVOID *)&pValueBuf, NULL))
		{
			VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)pValueBuf;
			bUpdateIsAvailable = dwUpdateMS > verInfo->dwProductVersionMS ||
				dwUpdateMS == verInfo->dwProductVersionMS && dwUpdateLS > verInfo->dwProductVersionLS;
		}
	}
	delete[] pVerBuf;

	if (bUpdateIsAvailable)
	{
		SimpleString updateQuestionFmt = SimpleString::LoadFromResource(hInst, IDS_UPDATE_QUESTION);
		SimpleString updateCaption = SimpleString::LoadFromResource(hInst, IDS_UPDATE_CAPTION);

		SimpleString updateQuestion = SimpleString::Format(updateQuestionFmt.ToCharArray(), fileBuffer.ToCharArray());
		if (IDYES == MessageBox(NULL, updateQuestion.ToCharArray(), updateCaption.ToCharArray(), MB_ICONQUESTION | MB_YESNO))
		{
			SimpleString updatePageURL = SimpleString::LoadFromResource(hInst, IDS_UPDATE_PAGE_URL);
			OpenHyperlink(updatePageURL);
		}
	}
}
